import logging
import struct
from collections import Counter

from odoo import _, api, fields, models
from odoo.exceptions import AccessError, ValidationError
from odoo.tests import Form
from odoo.tools import html2plaintext

from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES

_logger = logging.getLogger(__name__)


class Ecn(models.Model):
    _name = "ecn"
    _description = "Version Upgrade"
    _order = "date desc"

    # General fields
    name = fields.Char(index=True, readonly=True, copy=False, default=lambda self: _("New"))
    sale_order_id = fields.Many2one(comodel_name="sale.order", string="Sales Order")
    product_id = fields.Many2one(comodel_name="product.product", string="Product")
    product_new_id = fields.Many2one(comodel_name="product.product", string="Product (New Version)")
    allowed_levels = fields.One2many('ecn.level', compute='_compute_level_tags')

    start_level = fields.Many2one(comodel_name="ecn.level", string="Level",
                                  domain="[('id', 'in', allowed_levels)]")
    start_step = fields.Integer(string="Start Step", default=1)
    date = fields.Datetime(default=fields.Datetime.now, string="Date", index=True, required=True)
    user_id = fields.Many2one(comodel_name="res.users", string="Operator", index=True, tracking=True,
                              default=lambda self: self.env.user)
    ecn_line_mo_ids = fields.One2many(comodel_name="ecn.line", inverse_name="ecn_id", string="Business Detail Lines")
    state = fields.Selection(
        [
            ("draft", "Draft"),
            ('ready', 'Ready'),
            ("finished", "Finished"),
            ("cancelled", "Canceled"),
        ], default="draft", copy=False, tracking=True)
    description = fields.Html("Description")
    active = fields.Boolean("Active")
    level_tags = fields.Char("Level Tags", compute='_compute_level_tags')
    level_tags_new = fields.Char("New Level Tags", compute='_compute_level_tags_new')
    ecn_srce_records = fields.One2many("ecn.bom.wizards", inverse_name="ecn_id")
    ecn_dest_records = fields.One2many("ecn.bom.wizards", inverse_name="ecn_id")
    ecn_mo_records = fields.One2many("ecn.bom.wizards", inverse_name="ecn_id")

    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:
            if vals.get("name", _("New")) == _("New"):
                ir_sequence = self.env["ir.sequence"]
                if "company_id" in vals:
                    ir_sequence = ir_sequence.with_company(vals["company_id"])
                vals["name"] = ir_sequence.next_by_code("ecn")
            vals["active"] = True
            vals["state"] = "ready"
        ecns = super().create(vals_list)
        return ecns

    @api.onchange('sale_order_id')
    def _onchange_sale_order_id(self):
        if self.sale_order_id:
            for i in self.sale_order_id.order_line:
                self.product_id = i.product_id.id
                # self.ensure_one()

    def _getBomLevels(self, bom, rlevels, orgion):
        i = 0
        for b in bom.bom_line_ids:
            p = b.product_id
            prod = self.env['product.template'].search([
                ('id', '=', p.id), ], limit=9999)
            print(prod.name)
            print(prod.is_manufacture)
            print(prod.is_purchase)
            if prod.is_manufacture:
                boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(p,
                                                                                               picking_type=None,
                                                                                               company_id=self.sale_order_id.company_id.id,
                                                                                               bom_type='normal')
                bom = boms_by_product[p]
                i = i + 1
                s = orgion + "-" + str(i).zfill(2)
                # rlevels.append((s,s))
                rlevels.append(s)
                print(s + p.name)

                rlevels = self._getBomLevels(bom, rlevels, s)
        return rlevels

    def _getBomStruc(self, bom, rStruc, Level, iType):
        i = 0
        pLevel = Level
        for b in bom.bom_line_ids:
            p = b.product_id
            prod = self.env['product.template'].search([
                ('id', '=', p.id), ], limit=9999)
            if prod.is_manufacture:
                boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(p,
                                                                                               picking_type=None,
                                                                                               company_id=self.sale_order_id.company_id.id,
                                                                                               bom_type='normal')
                bom = boms_by_product[p]
                i = i + 1
                s = Level + "-" + str(i).zfill(2)
                rStruc.append((0, 0, {
                    'ecn_id': self.id,
                    'origin': self.sale_order_id.name,
                    'record_type': 1,
                    'product_id': prod.id,
                    'quan_bom': 1,
                    'value_pnl': prod.value_pnl,
                    'value_set': prod.value_set,
                    'level': s,
                    'plevel': pLevel,
                    'is_m': "M"
                }))
                rStruc = self._getBomStruc(bom, rStruc, s, iType)
            else:
                rStruc.append((0, 0, {
                    'ecn_id': self.id,
                    'origin': self.sale_order_id.name,
                    'record_type': iType,
                    'product_id': prod.id,
                    'quan_bom': b.product_qty,
                    'value_pnl': 0,
                    'value_set': 0,
                    'level': pLevel,
                    'plevel': pLevel,
                    'is_m': "P"
                }))
        return rStruc

    def _getMOStruc(self, mo, rStruc, Level, iType):
        pLevel = Level
        mos = self.env['mrp.production'].search([
            ('origin', 'like', mo), ], limit=9999)
        if mos:
            i = 0
            for m in mos:
                i = i + 1
                s = Level + "-" + str(i).zfill(2)
                rStruc = self._getMOStruc(m.name, rStruc, s, 2)
                rStruc.append((0, 0, {
                    'ecn_id': self.id,
                    'origin': self.sale_order_id.name,
                    'record_type': 3,
                    'production_id': m.id,
                    'quan_bom': 1,
                    'value_pnl': 0,
                    'value_set': 0,
                    'level': s,
                    'plevel': pLevel,
                    'is_m': "M"
                }))
        return rStruc

    def _getMOLevels(self, mo, rLevels, orgion):
        mos = self.env['mrp.production'].search([
            ('origin', 'like', mo), ], limit=9999)
        if mos:
            i = 0
            for m in mos:
                i = i + 1
                s = orgion + "-" + str(i).zfill(2)
                rLevels.append(s)
                rLevels = self._getMOLevels(m.name, rLevels, s)
        return rLevels

    @api.depends('product_id')
    def _compute_level_tags(self):
        rlevels = []
        if self.product_id:
            boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(self.product_id,
                                                                                           picking_type=None,
                                                                                           company_id=self.sale_order_id.company_id.id,
                                                                                           bom_type='normal')
            bom = boms_by_product[self.product_id]
            # rlevels.append(("R","R"))
            rlevels.append("R")
            prod = self.env['product.template'].search([
                ('id', '=', self.product_id.id), ], limit=9999)
            # temp_lines = []
            # temp_lines.append((0,0,{
            #     #'ecn_id': self.id,
            #     'origin': self.sale_order_id.name,
            #     'record_type': 1,
            #     'product_id': self.product_id.id,
            #     'quan_bom':1,
            #     'value_pnl': prod.value_pnl,
            #     'value_set': prod.value_set,
            #     'level': "R",
            #     'is_m':"R"
            # }))
            # print(temp_lines)
            # self.ecn_temp_records = temp_lines
            rlevels = self._getBomLevels(bom, rlevels, "R")
        # logging.info(rlevels)
        tags = ','.join(rlevels)
        # logging.info(tags)
        self.level_tags = tags
        self.allowed_levels = self.env['ecn.level'].search([('name', 'in', rlevels)], limit=9999)
        # logging.info(self.allowed_levels.ids)

    @api.depends('product_new_id')
    def _compute_level_tags_new(self):
        rlevels = []
        if self.product_new_id:
            boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(self.product_new_id,
                                                                                           picking_type=None,
                                                                                           company_id=self.sale_order_id.company_id.id,
                                                                                           bom_type='normal')
            bom = boms_by_product[self.product_new_id]
            # rlevels.append(("R","R"))
            rlevels.append("R")
            rlevels = self._getBomLevels(bom, rlevels, "R")
        logging.info(rlevels)
        tags = ','.join(rlevels)
        # logging.info(tags)
        self.level_tags_new = tags

    def _get_related_mos(self, origin, ids):
        # ids = []
        mos = self.env['mrp.production'].search([
            ('origin', 'like', origin), ], limit=9999)
        print(origin)
        if mos:
            for m in mos:
                ids.append(m.id)
                # ids = self._get_related_mos(m.name,ids) 是否增加内层
        return ids

    @api.onchange('start_level')
    def _onchange_start_level(self):
        if self.level_tags_new != self.level_tags:
            raise ValidationError(
                _("Inconsistency between the inner and outer structure of the new version") + self.level_tags_new
            )
        # print(self.ecn_temp_records)
        # self.ecn_line_mo_ids.unlink()
        if self.start_level:
            # source bom
            strucs = []
            boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(self.product_id,
                                                                                           picking_type=None,
                                                                                           company_id=self.sale_order_id.company_id.id,
                                                                                           bom_type='normal')
            bom = boms_by_product[self.product_id]
            prod = self.env['product.template'].search([
                ('id', '=', self.product_id.id), ], limit=9999)
            strucs.append((0, 0, {
                'ecn_id': self.id,
                'origin': self.sale_order_id.name,
                'record_type': 1,
                'product_id': self.product_id.id,
                'quan_bom': 1,
                'value_pnl': prod.value_pnl,
                'value_set': prod.value_set,
                'level': "R",
                'plevel': "",
                'is_m': "R"
            }))
            strucs = self._getBomStruc(bom, strucs, "R", 1)
            self.ecn_srce_records = strucs
            # dest bom
            dstrucs = []
            boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(self.product_new_id,
                                                                                           picking_type=None,
                                                                                           company_id=self.sale_order_id.company_id.id,
                                                                                           bom_type='normal')
            bom = boms_by_product[self.product_new_id]
            prod = self.env['product.template'].search([
                ('id', '=', self.product_new_id.id), ], limit=9999)
            dstrucs.append((0, 0, {
                'ecn_id': self.id,
                'origin': self.sale_order_id.name,
                'record_type': 2,
                'product_id': self.product_new_id.id,
                'quan_bom': 1,
                'value_pnl': prod.value_pnl,
                'value_set': prod.value_set,
                'level': "R",
                'plevel': "",
                'is_m': "R"
            }))
            dstrucs = self._getBomStruc(bom, dstrucs, "R", 2)
            self.ecn_dest_records = dstrucs

            # print(strucs)
            # for tmp in self.ecn_temp_records:
            #     print(tmp.level + tmp.product_id.name)
            # print(self.start_level)
            if self.sale_order_id:
                print(self.sale_order_id)
                if not self.ecn_line_mo_ids:
                    print("sss")
                    so = self.sale_order_id.name
                    ids = []
                    ids = self._get_related_mos(so, ids)
                    mos = self.env['mrp.production'].search([('id', 'in', ids)], limit=9999)
                    temp_lines = []
                    for m in mos:
                        moLevels = []
                        moLevels.append("R")
                        # 1.检查物料是否存在目标BOM.2.检查物料是否已发放
                        mostruc = []
                        mostruc.append((0, 0, {
                            'ecn_id': self.id,
                            'origin': self.sale_order_id.name,
                            'record_type': 3,
                            'production_id': m.id,
                            'quan_bom': 1,
                            'value_pnl': 0,
                            'value_set': 0,
                            'level': "R",
                            'plevel': "",
                            'is_m': "R"
                        }))
                        mostruc = self._getMOStruc(m.name, mostruc, "R", 3)
                        self.ecn_mo_records = False
                        self.ecn_mo_records = mostruc

                        print("MO:")
                        for tmp in self.ecn_mo_records:
                            print(tmp.level)
                            print(tmp.production_id.name)

                        # issue_reason = self._ValidRawIssue(strucs,m)

                        moLevels = self._getMOLevels(m.name, moLevels, "R")

                        moLevelsTags = ','.join(moLevels)
                        # logging.info(moLevels)
                        sReason = self._valid_mo(moLevelsTags)
                        sReason = sReason.strip()
                        logging.info(sReason)
                        allow = True
                        if (len(sReason) > 0):
                            allow = False
                        temp_lines.append((0, 0,
                                           {
                                               'ecn_id': self.id,
                                               'production_id': m.id,
                                               # 'root_bom_id': m.bom_id.id,
                                               # 'bom_id': m.bom_id.id,
                                               # 'bom_line_id': m.bom_id.id,
                                               'is_allow': allow,
                                               'reason': sReason
                                           }
                                           ))
                    self.ecn_line_mo_ids = temp_lines

    def _valid_route(self, mo, level):
        dbom = self.ecn_dest_records
        reason = ""
        sstep = self.start_step
        slevel = self.start_level.name

        for db in dbom:
            if db.level != level:
                continue
            if db.is_m == "P":
                continue
            # 检查流程
            br = []
            p = db.product_id
            prod = self.env['product.template'].search([
                ('id', '=', p.id), ], limit=9999)
            boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(p,
                                                                                           picking_type=None,
                                                                                           company_id=self.sale_order_id.company_id.id,
                                                                                           bom_type='normal')
            bom = boms_by_product[p]
            mor = []
            if db.level == slevel:
                for o in bom.operation_ids:
                    if sstep < o.step:
                        s = _("Procedure:%i,Workshop:%s(%i)" % (o.step, o.workcenter_id.name, o.workcenter_id.id))
                        br.append(s)
                        # br.append([o.step, o.workcenter_id.id, o.workcenter_id.name])
                print(br)
                for wo in mo.workorder_ids:
                    if sstep < wo.step:
                        s = _("Procedure:%i,Workshop:%s(%i)" % (wo.step, wo.workcenter_id.name, wo.workcenter_id.id))
                        mor.append(s)
                    else:
                        reason = ""
                        if wo.state == 'done' or wo.done_act_qty > 0:
                            reason = _("Process %s Processed", wo.workcenter_id.name.strip())
                            return reason
                        # mor.append([wo.step, wo.workcenter_id.id, wo.workcenter_id.name])
                print(mor)
            else:
                for o in bom.operation_ids:
                    s = _("Procedure:%i,Workshop:%s(%i)" % (o.step, o.workcenter_id.name, o.workcenter_id.id))
                    br.append(s)
                    # br.append([o.step, o.workcenter_id.id, o.workcenter_id.name])
                print(br)
                for wo in mo.workorder_ids:
                    s = _("Procedure:%i,Workshop:%s(%i)" % (wo.step, wo.workcenter_id.name, wo.workcenter_id.id))
                    mor.append(s)
                    # mor.append([wo.step, wo.workcenter_id.id, wo.workcenter_id.name])
                print(mor)
            reason = ""
            diff = set(br) - set(mor)
            if len(diff) > 0:
                s = ","
                for df in diff:
                    s = _(s + "Procedure:" + df[1] + ",Workshop:" + df[2])
                reason = _(db.level + "Process discrepancy" + s)
                break
            # 注意指定层的，及指定层后的步骤排除
        return reason

    def _valid_mo(self, moLevelTags):
        sreason = ""
        if moLevelTags != self.level_tags:
            sreason = _("The inner structure does not match")
            return sreason
        sbom = self.ecn_srce_records
        dbom = self.ecn_dest_records
        mos = self.ecn_mo_records
        for m in mos:
            mo = self.env['mrp.production'].search([('id', '=', m.production_id.id)], limit=1)
            # 检查是否已产出成品
            if m.level == "R":
                if mo.qty_produced > 0:
                    sreason = _(mo.name + 'Produced')
                    return sreason
            # 检查原材料是否发料
            for i in mo.move_raw_ids:
                if i.state in ['done']:
                    found = False
                    iproduct_id = i.ori_product_id
                    unit_factor = i.ori_unit_factor
                    for db in dbom:
                        if db.plevel != m.plevel:
                            continue
                        if db.product_id == iproduct_id:
                            found = True
                            # bom用量检查
                            if db.quan_bom != unit_factor:
                                sreason = _(i.product_id.name + 'Dispatched, inconsistent dosage.')
                                return sreason
                    if not found:
                        sreason = _(i.product_id.name + 'Shipped, not available in target structure')
                        return sreason
            # 检查流程
            # mo.workorder_ids
            # bom.operation_ids
            sreason = self._valid_route(mo, m.level)
        return sreason

    def _getDestItemForLevel(self, level):
        for b in self.ecn_dest_records:
            if b.level == level and b.is_m != 'P':
                return b
        return False

    def _uptMO(self, dest, mo):
        return False

    def _UptMOs(self):
        for mr in self.ecn_mo_records:
            mo = self.env['mrp.production'].search([('id', '=', mr.production_id.id)], limit=1)
            ditem = self._getDestItemForLevel(mo.level)
            rs = self._uptMO(ditem, mo)
            if not rs:
                return False
        return True

    def button_mark_done(self):
        # 更新SALES ORDER Line
        for sol in self.sale_order_id.order_line:
            if sol.product_id.id == self.product_id.id:
                if sol.qty_delivered <= 0:
                    sol.write({
                        "product_id": self.product_new_id.id,
                    })
        # 更新SALES ORDER
        # 更新关联MO，WO，STOCK MOVE
        dstrucs = []
        boms_by_product = self.env['mrp.bom'].with_context(active_test=True)._bom_find(self.product_new_id,
                                                                                       picking_type=None,
                                                                                       company_id=self.sale_order_id.company_id.id,
                                                                                       bom_type='normal')
        bom = boms_by_product[self.product_new_id]
        prod = self.env['product.template'].search([
            ('id', '=', self.product_new_id.id), ], limit=9999)
        dstrucs.append((0, 0, {
            'ecn_id': self.id,
            'origin': self.sale_order_id.name,
            'record_type': 2,
            'product_id': self.product_new_id.id,
            'quan_bom': 1,
            'value_pnl': prod.value_pnl,
            'value_set': prod.value_set,
            'level': "R",
            'plevel': "",
            'is_m': "R"
        }))
        dstrucs = self._getBomStruc(bom, dstrucs, "R", 2)
        self.ecn_dest_records = dstrucs
        for line in self.ecn_line_mo_ids:
            if line.is_allow:
                mo = self.env['mrp.production'].search([('id', '=', line.production_id.id)], limit=1)
                mostruc = []
                mostruc.append((0, 0, {
                    'ecn_id': self.id,
                    'origin': self.sale_order_id.name,
                    'record_type': 3,
                    'production_id': mo.id,
                    'quan_bom': 1,
                    'value_pnl': 0,
                    'value_set': 0,
                    'level': "R",
                    'plevel': "",
                    'is_m': "R"
                }))
                mostruc = self._getMOStruc(mo.name, mostruc, "R", 3)
                self.ecn_mo_records = False
                self.ecn_mo_records = mostruc
        # end
        return True


class EcnLine(models.Model):
    _name = "ecn.line"
    _description = "Version upgrade details"
    _order = "production_id desc"
    ecn_id = fields.Many2one(comodel_name="ecn", string="ECN Order Number", ondelete="cascade")
    production_id = fields.Many2one('mrp.production', 'Manufacturing Order', required=True)
    # root_bom_id = fields.Many2one('mrp.bom', 'Bill of Material', readonly=False)
    # bom_id = fields.Many2one('mrp.bom', 'Bill of Material', readonly=False)
    # bom_line_id = fields.Many2one('mrp.bom.line', 'BoM Line')
    is_allow = fields.Boolean(string="Whether to allow upgrades")
    reason = fields.Char(string="Reason")


class EcnLevel(models.Model):
    _name = "ecn.level"
    _description = "BOM Level"
    _order = "name asc"
    name = fields.Char(string="Level")
